#include "common/pch.h"

#include <cftc/reflect/class.h>
#include <cftc/reflect/field.h>

#include "dbc/loader.h"

namespace dbc
{

    Loader::Loader()
    {
        data_ = nullptr;
        fields_offset_ = nullptr;
    }

    bool Loader::Load(const char* filename, const char* fmt)
    {
        fmt_ = fmt;
        unsigned int header;
        delete[] data_;

        FILE* f = nullptr;
        fopen_s(&f, filename, "rb");
        if (!f)
        {
            return false;
        }

        if (fread(&header, 4, 1, f) != 1)                       // Number of records
        {
            fclose(f);
            return false;
        }

        //EndianConvert(header);
        if (header != 0x43424457)                               //'WDBC'
        {
            fclose(f);
            return false;
        }

        if (fread(&record_count_, 4, 1, f) != 1)                  // Number of records
        {
            fclose(f);
            return false;
        }

        //EndianConvert(recordCount);

        if (fread(&field_count_, 4, 1, f) != 1)                   // Number of fields
        {
            fclose(f);
            return false;
        }

        //EndianConvert(fieldCount);

        if (fread(&record_size_, 4, 1, f) != 1)                   // Size of a record
        {
            fclose(f);
            return false;
        }

        //EndianConvert(recordSize);

        if (fread(&string_size_, 4, 1, f) != 1)                   // String size
        {
            fclose(f);
            return false;
        }

        //EndianConvert(stringSize);

        fields_offset_ = new unsigned int[field_count_];
        fields_offset_[0] = 0;
        for (unsigned int i = 1; i < field_count_; ++i)
        {
            fields_offset_[i] = fields_offset_[i - 1];
            if (fmt[i - 1] == 'b' || fmt[i - 1] == 'X')         // byte fields
            {
                fields_offset_[i] += 1;
            }
            else                                                // 4 byte fields (int/float/strings)
            {
                fields_offset_[i] += 4;
            }
        }

        data_ = new unsigned char[record_size_ * record_count_ + string_size_];
        string_table_ = data_ + record_size_ * record_count_;

        if (fread(data_, record_size_ * record_count_ + string_size_, 1, f) != 1)
        {
            fclose(f);
            return false;
        }

        fclose(f);
        return true;
    }

    Loader::~Loader()
    {
        delete[] data_;
        data_ = nullptr;
        delete[] fields_offset_;
        fields_offset_ = nullptr;
    }

    std::shared_ptr<Loader::Record> Loader::GetRecord(size_t id)
    {
        assert(data_);
        return std::make_shared<Loader::Record>(Record(*this, data_ + id * record_size_));
    }

    unsigned int Loader::GetFormatRecordSize(const char* format, int* index_pos)
    {
        unsigned int recordsize = 0;
        int i = -1;
        for (unsigned int x = 0; format[x]; ++x)
        {
            switch ((DBCFieldFormat_e)format[x])
            {
            case DBCFieldFormat_e::DBC_FF_FLOAT:
                recordsize += sizeof(float);
                break;
            case DBCFieldFormat_e::DBC_FF_INT:
                recordsize += sizeof(unsigned int);
                break;
            case DBCFieldFormat_e::DBC_FF_STRING:
                recordsize += sizeof(char*);
                break;
            case DBCFieldFormat_e::DBC_FF_SORT:
                i = x;
                break;
            case DBCFieldFormat_e::DBC_FF_IND:
                i = x;
                recordsize += sizeof(unsigned int);
                break;
            case DBCFieldFormat_e::DBC_FF_BYTE:
                recordsize += sizeof(unsigned char);
                break;
            case DBCFieldFormat_e::DBC_FF_LOGIC:
                assert(false && "Attempted to load DBC files that do not have field types that match what is in the core. Check DBCfmt.h or your DBC files.");
                break;
            case DBCFieldFormat_e::DBC_FF_NA:
            case DBCFieldFormat_e::DBC_FF_NA_BYTE:
                break;
            default:
                assert(false && "Unknown field format character in DBCfmt.h");
                break;
            }
        }

        if (index_pos)
        {
            *index_pos = i;
        }

        return recordsize;
    }

    char* Loader::AutoProduceData(const char* format, unsigned int& records, char**& indexTable)
    {
        /*
        format STRING, NA, FLOAT,NA,INT <=>
        struct{
        char* field0,
        float field1,
        int field2
        }entry;

        this func will generate  entry[rows] data;
        */

        typedef char* ptr;
        if (strlen(format) != field_count_)
        {
            return nullptr;
        }

        // get struct size and index pos
        int i;
        unsigned int recordsize = GetFormatRecordSize(format, &i);

        if (i >= 0)
        {
            unsigned int maxi = 0;
            // find max index
            for (unsigned int y = 0; y < record_count_; ++y)
            {
                unsigned int ind = GetRecord(y)->getUInt(i);
                if (ind > maxi)
                {
                    maxi = ind;
                }
            }

            ++maxi;
            records = maxi;
            indexTable = new ptr[maxi];
            memset(indexTable, 0, maxi * sizeof(ptr));
        }
        else
        {
            records = record_count_;
            indexTable = new ptr[record_count_];
        }

        char* dataTable = new char[record_count_ * recordsize];

        unsigned int offset = 0;

        for (unsigned int y = 0; y < record_count_; ++y)
        {
            if (i >= 0)
            {
                indexTable[GetRecord(y)->getUInt(i)] = &dataTable[offset];
            }
            else
            {
                indexTable[y] = &dataTable[offset];
            }

            for (unsigned int x = 0; x < field_count_; ++x)
            {
                switch ((DBCFieldFormat_e)format[x])
                {
                case DBCFieldFormat_e::DBC_FF_FLOAT:
                    *((float*)(&dataTable[offset])) = GetRecord(y)->getFloat(x);
                    offset += sizeof(float);
                    break;
                case DBCFieldFormat_e::DBC_FF_IND:
                case DBCFieldFormat_e::DBC_FF_INT:
                    *((unsigned int*)(&dataTable[offset])) = GetRecord(y)->getUInt(x);
                    offset += sizeof(unsigned int);
                    break;
                case DBCFieldFormat_e::DBC_FF_BYTE:
                    *((unsigned char*)(&dataTable[offset])) = GetRecord(y)->getUInt8(x);
                    offset += sizeof(unsigned char);
                    break;
                case DBCFieldFormat_e::DBC_FF_STRING:
                    *((char**)(&dataTable[offset])) = nullptr; // will replace non-empty or "" strings in AutoProduceStrings
                    offset += sizeof(char*);
                    break;
                case DBCFieldFormat_e::DBC_FF_LOGIC:
                    assert(false && "Attempted to load DBC files that do not have field types that match what is in the core. Check DBCfmt.h or your DBC files.");
                    break;
                case DBCFieldFormat_e::DBC_FF_NA:
                case DBCFieldFormat_e::DBC_FF_NA_BYTE:
                case DBCFieldFormat_e::DBC_FF_SORT:
                    break;
                default:
                    assert(false && "Unknown field format character in DBCfmt.h");
                    break;
                }
            }
        }

        return dataTable;
    }

    char* Loader::AutoProduceStrings(const char* format, char* dataTable)
    {
        if (strlen(format) != field_count_)
        {
            return nullptr;
        }

        char* stringPool = new char[string_size_];
        memcpy(stringPool, string_table_, string_size_);

        unsigned int offset = 0;

        for (unsigned int y = 0; y < record_count_; ++y)
        {
            for (unsigned int x = 0; x < field_count_; ++x)
            {
                switch ((DBCFieldFormat_e)format[x])
                {
                case DBCFieldFormat_e::DBC_FF_FLOAT:
                    offset += sizeof(float);
                    break;
                case DBCFieldFormat_e::DBC_FF_IND:
                case DBCFieldFormat_e::DBC_FF_INT:
                    offset += sizeof(unsigned int);
                    break;
                case DBCFieldFormat_e::DBC_FF_BYTE:
                    offset += sizeof(unsigned char);
                    break;
                case DBCFieldFormat_e::DBC_FF_STRING:
                {
                    // fill only not filled entries
                    char** slot = (char**)(&dataTable[offset]);
                    if (!*slot || !**slot)
                    {
                        const char* st = GetRecord(y)->getString(x);
                        *slot = stringPool + (st - (const char*)string_table_);
                    }
                    offset += sizeof(char*);
                    break;
                }
                case DBCFieldFormat_e::DBC_FF_LOGIC:
                    assert(false && "Attempted to load DBC files that does not have field types that match what is in the core. Check DBCfmt.h or your DBC files.");
                    break;
                case DBCFieldFormat_e::DBC_FF_NA:
                case DBCFieldFormat_e::DBC_FF_NA_BYTE:
                case DBCFieldFormat_e::DBC_FF_SORT:
                    break;
                default:
                    assert(false && "Unknown field format character in DBCfmt.h");
                    break;
                }
            }
        }

        return stringPool;
    }

}